home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Audio, Video & Photo / Songbird 0.7.0 / Songbird_0.7.0_windows-i686-msvc8.exe / components / sbBookmarksService.js < prev    next >
Text File  |  2008-08-06  |  25KB  |  743 lines

  1. /** vim: ts=2 sw=2 expandtab
  2. //
  3. // BEGIN SONGBIRD GPL
  4. //
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2008 POTI, Inc.
  8. // http://songbirdnest.com
  9. //
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. //
  13. // Software distributed under the License is distributed
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
  15. // express or implied. See the GPL for the specific language
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc.,
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. //
  23. // END SONGBIRD GPL
  24. //
  25.  */
  26.  
  27. /**
  28.  * \file sbBookmarks.js
  29.  * \brief the service pane service manages the tree behind the service pane.
  30.  * It provides an interface for creating bookmark nodes in the service pane tree.
  31.  *
  32.  * TODO:
  33.  *  - move downloading to nsIChannel and friends for better chrome-proofing
  34.  *  - send version and locale info when requesting new bookmarks
  35.  *  - handle errors more elegantly in bookmarks downloading
  36.  *  - handle the adding of bookmarks before the defaults are downloaded
  37.  *  - allow the server bookmarks list to remove stale entries [bug 2352]
  38.  * PERHAPS:
  39.  *  - move default bookmarks downloading to after first-run so we can have
  40.  *  per-locale bookmarks?
  41.  *  - download and cache images
  42.  *  - add a confirmation dialog for for bookmark deletion
  43.  *  
  44.  */
  45.  
  46. const Cc = Components.classes;
  47. const Ci = Components.interfaces;
  48. const Cr = Components.results;
  49. const Cu = Components.utils;
  50.  
  51. const CONTRACTID = "@songbirdnest.com/servicepane/bookmarks;1"
  52. const ROOTNODE = "SB:Bookmarks"
  53. const BOOKMARK_DRAG_TYPE = 'text/x-sb-bookmark';
  54. const MOZ_URL_DRAG_TYPE = 'text/x-moz-url';
  55. const BSP = 'http://songbirdnest.com/rdf/bookmarks#';
  56. const SP='http://songbirdnest.com/rdf/servicepane#';
  57.  
  58. function SB_NewDataRemote(a,b) {
  59.   return (new Components.Constructor("@songbirdnest.com/Songbird/DataRemote;1",
  60.                     "sbIDataRemote", "init"))(a,b);
  61. }
  62.  
  63. function sbBookmarks() {
  64.   this._servicePane = null;
  65.   this._stringBundle = null;
  66.   
  67.   // use the default stringbundle to translate tree nodes
  68.   this.stringbundle = null;
  69.  
  70.   this._importAttempts = 5;
  71.   this._importTimer = null;
  72. }
  73. sbBookmarks.prototype.QueryInterface = 
  74. function sbBookmarks_QueryInterface(iid) {
  75.   if (!iid.equals(Ci.nsISupports) &&
  76.     !iid.equals(Ci.sbIBookmarks) &&
  77.     !iid.equals(Ci.sbIServicePaneModule)) {
  78.     throw Cr.NS_ERROR_NO_INTERFACE;
  79.   }
  80.   return this;
  81. }
  82. sbBookmarks.prototype.servicePaneInit = 
  83. function sbBookmarks_servicePaneInit(sps) {
  84.   this._servicePane = sps;
  85.   
  86.   Components.utils.import("resource://app/jsmodules/SBDataRemoteUtils.jsm");
  87.  
  88.   // if we don't have a bookmarks node, lets create one
  89.   this._bookmarkNode = this._servicePane.getNode(ROOTNODE);
  90.   if (!this._bookmarkNode) {
  91.     // create bookmarks folder
  92.     this._bookmarkNode = this.addFolderAt('SB:Bookmarks',
  93.         '&servicesource.bookmarks', null, sps.root, null);
  94.   }
  95.  
  96.   // set the weight of the bookmarks node
  97.   this._bookmarkNode.setAttributeNS(SP, 'Weight', 4);
  98.   
  99.   // if a flag was set by the safe mode dialog to signify that we should
  100.   // restore the default bookmarks, do so by first deleting all the current
  101.   // ones, and then forcing an import like it would happen on first run.
  102.   var restoreDefault = false;
  103.   var prefBranch  = Components.classes["@mozilla.org/preferences-service;1"]
  104.                               .getService(Components.interfaces.nsIPrefBranch);
  105.   try {
  106.     restoreDefault = prefBranch.getBoolPref("browser.bookmarks.restore_default_bookmarks");
  107.   } catch (e) { }
  108.   if (restoreDefault) {
  109.     // remove all current bookmarks
  110.     while (this._bookmarkNode.firstChild != null) {
  111.       this._servicePane.removeNode(this._bookmarkNode.firstChild);
  112.     }
  113.     // forget that we did the import already
  114.     this._bookmarkNode.setAttributeNS(BSP, 'Imported', 'false');
  115.     // reset the safe mode flag so we don't keep doing this over and over
  116.     prefBranch.setBoolPref("browser.bookmarks.restore_default_bookmarks", "false");
  117.   }
  118.     
  119.   // if the bookmark node doesn't have the Imported attribute set, lets do an import
  120.   if (this._bookmarkNode.getAttributeNS(BSP, 'Imported') != 'true') {
  121.     // run the importer
  122.     this.importBookmarks();
  123.   }
  124.  
  125.   var sbSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
  126.   this._stringBundle = sbSvc.createBundle("chrome://songbird/locale/songbird.properties");
  127. }
  128.  
  129. sbBookmarks.prototype.scheduleImportBookmarks =
  130. function sbBookmarks_scheduleImportBookmarks() {
  131.   this._importTimer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
  132.   this._importTimer.init(this, 5000, Ci.nsITimer.TYPE_ONE_SHOT);
  133. }
  134.  
  135. sbBookmarks.prototype.observe = 
  136. function sbBookmarks_observe(subject, topic, data) {
  137.   if (topic == 'timer-callback' && subject == this._importTimer) {
  138.     // the bookmarks import timer
  139.     this._importTimer = null;
  140.     if (!this._servicePane) {
  141.       // hmm, we're shutting down, no need to import now
  142.       return;
  143.     }
  144.     this.importBookmarks();
  145.   }
  146. }
  147.  
  148. sbBookmarks.prototype.importBookmarks =
  149. function sbBookmarks_importBookmarks() {
  150.   var prefsService =
  151.       Cc["@mozilla.org/preferences-service;1"].
  152.       getService(Ci.nsIPrefBranch);
  153.   var bookmarksURL = prefsService.getCharPref("songbird.url.bookmarks");
  154.   
  155.   // fetch the default set of bookmarks through a series of tubes
  156.   // FIXME: don't use XHR - use nsIChannel and friends
  157.   // FIXME: send parameters and/or headers to indicate product version or something
  158.   var xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
  159.       .createInstance(Ci.nsIDOMEventTarget);
  160.   var sps = this._servicePane;
  161.   var service = this;
  162.   function importError() {
  163.     service._importAttempts--;
  164.     if (service._importAttempts < 0) {
  165.       // we tried, but it's time to give up till the next time the player starts
  166.       // but first, let's create some default bookmarks to get us through
  167.       var default_bookmarks = [
  168.         {name:'Add-ons', url:'http://addons.songbirdnest.com/'},
  169.         {name:'Directory', url:'http://birdhouse.songbirdnest.com/directory'}];
  170.       for (var i=0; i<default_bookmarks.length; i++) {
  171.         var bm = default_bookmarks[i];
  172.         var bnode = sps.getNode(bm.url);
  173.         if (!bnode) {
  174.           service.addBookmarkAt(bm.url, bm.name, 
  175.             'chrome://songbird-branding/skin/logo_16.png', 
  176.             service._bookmarkNode, null);
  177.         }
  178.       }
  179.       
  180.       return;
  181.     }
  182.     service.scheduleImportBookmarks();
  183.   }
  184.   xhr.addEventListener('load', function(evt) {
  185.     var root = null;
  186.     try {
  187.       // a non-XML page will cause an exception here.
  188.       root = xhr.responseXML.documentElement;
  189.     } catch (e) {
  190.       // catch it and try again
  191.       importError();
  192.       return;
  193.     }
  194.     var folders = root.getElementsByTagName('folder');
  195.     for (var f=0; folders && f<folders.length; f++) {
  196.       var folder = folders[f];
  197.       if (!folder.hasAttribute('id') ||
  198.           !folder.hasAttribute('name')) {
  199.         // the folder is missing required attributes, we must ignore it
  200.         continue;
  201.       }
  202.  
  203.       var fnode = sps.getNode(folder.getAttribute('id'));
  204.       
  205.       if (!fnode) {
  206.         // if the folder doesn't exist, create it
  207.         fnode = service.addFolderAt(folder.getAttribute('id'),
  208.             folder.getAttribute('name'), folder.getAttribute('image'),
  209.             sps.root, null);
  210.       }
  211.       
  212.       fnode.isOpen = (folder.getAttribute('open') == 'true');
  213.       
  214.       if (fnode && fnode.getAttributeNS(BSP, 'Imported') == 'true') {
  215.         // don't reimport a folder that's already been imported
  216.         continue;
  217.       }
  218.       
  219.       if (fnode.id == ROOTNODE) {
  220.         // we just created the default bookmarks root
  221.         // we'll need that later if we want to let the user create
  222.         // their own bookmarks
  223.         service._bookmarkNode = fnode;
  224.       }
  225.       
  226.       // now, let's create what goes in
  227.       var bookmarks = folder.getElementsByTagName('bookmark');
  228.       for (var b=0; bookmarks && b<bookmarks.length; b++) {
  229.         var bookmark = bookmarks[b];
  230.         if (!bookmark.hasAttribute('url') ||
  231.             !bookmark.hasAttribute('name')) {
  232.           // missing required attributes
  233.           continue;
  234.         }
  235.         // If the bookmark already exists, then it's somewhere else
  236.         // in the tree and we should leave it there untouched.
  237.         // Except that it should be marked as imported now...
  238.         var bnode = sps.getNode(bookmark.getAttribute('url'));
  239.         if (!bnode) {
  240.           // create the bookmark
  241.           bnode = service.addBookmarkAt(bookmark.getAttribute('url'),
  242.               bookmark.getAttribute('name'), bookmark.getAttribute('image'),
  243.               fnode, null);
  244.         }
  245.         // remember we imported it.
  246.         bnode.setAttributeNS(BSP, 'Imported', 'true');
  247.       }
  248.       
  249.       fnode.setAttributeNS(BSP, 'Imported', 'true');
  250.     }
  251.     
  252.     // try to import json bookmarks from 0.2.5
  253.     try {
  254.       service.migrateLegacyBookmarks();
  255.     } catch (e) {
  256.     }
  257.  
  258.   }, false);
  259.   xhr.addEventListener('error', function(evt) {
  260.     importError();
  261.   }, false);
  262.  
  263.   // XXXredfive - this will(may) change to the mozilla urlformatter when
  264.   //  bmo 430235 gets fixed.
  265.   // use the urlFormatter service to replace the %FOO% mumbo-jumbo
  266.   var urlFormatter = Components.classes["@songbirdnest.com/moz/sburlformatter;1"]
  267.                        .getService(Components.interfaces.sbIURLFormatter);
  268.  
  269.   var pbag = Components.classes["@mozilla.org/hash-property-bag;1"]
  270.                        .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  271.  
  272.   bookmarksURL = urlFormatter.formatURL(bookmarksURL, pbag);
  273.  
  274.   xhr.QueryInterface(Ci.nsIXMLHttpRequest);
  275.   xhr.open('GET', bookmarksURL, true);
  276.   xhr.send(null);
  277. }
  278.  
  279. sbBookmarks.prototype.shutdown = 
  280. function sbBookmarks_shutdown() {
  281.   this._bookmarkNode = null;
  282.   this._servicePane = null;
  283.   this._stringBundle = null;
  284.   this._importAttempts = -1; // do not make any more attempts to import
  285.   if (this._importTimer) {
  286.     this._importTimer.cancel();
  287.     this._importTimer = null;
  288.   }
  289. }
  290.  
  291. sbBookmarks.prototype.getString =
  292. function sbBookmarks_getString(aStringId, aDefault) {
  293.   try {
  294.     return this._stringBundle.GetStringFromName(aStringId);
  295.   } catch (e) {
  296.     return aDefault;
  297.   }
  298. }
  299. sbBookmarks.prototype.migrateLegacyBookmarks =
  300. function sbBookmarks_migrateLegacyBookmarks() {
  301.   try {
  302.     var prefsService =
  303.         Cc["@mozilla.org/preferences-service;1"].
  304.         getService(Ci.nsIPrefBranch);
  305.     var LEGACY_BOOKMARKS_PREF = 'songbird.bookmarks.serializedTree';
  306.     if (prefsService.prefHasUserValue(LEGACY_BOOKMARKS_PREF)) {
  307.       var json = '(' + prefsService.getCharPref(LEGACY_BOOKMARKS_PREF) + ')';
  308.       var bms = eval(json);
  309.       for (var i in bms.children) {
  310.         var folder = bms.children[i];
  311.         if (folder.label == '&servicesource.bookmarks') {
  312.           for (var j in folder.children) {
  313.             var bm = folder.children[j];
  314.             if (bm.properties != 'bookmark') {
  315.               continue;
  316.             }
  317.             var node = this._servicePane.getNode(bm.url);
  318.             if (node) {
  319.               // this bookmark already existed.
  320.               // we want to set the title to the old
  321.               node.name = bm.label;
  322.             } else {
  323.               // the bookmark does not exist. We need to create it
  324.               var icon = null;
  325.               // only import the icon if its from the web
  326.               if (bm.icon.match(/^http/)) {
  327.                 icon = bm.icon;
  328.               }
  329.               this.addBookmark(bm.url, bm.label, icon);
  330.             }
  331.           }
  332.           break;
  333.         }
  334.       }
  335.       // let's clear that pref
  336.       prefsService.clearUserPref(LEGACY_BOOKMARKS_PREF);
  337.     }
  338.   } catch (e) {
  339.   }
  340. }
  341.  
  342. sbBookmarks.prototype.addBookmark =
  343. function sbBookmarks_addBookmark(aURL, aTitle, aIconURL) {
  344.   dump('sbBookmarks.addBookmark('+aURL.toSource()+','+aTitle.toSource()+','+aIconURL.toSource()+')\n');
  345.   if (!this._bookmarkNode) {
  346.     // if we try to add a bookmark the defaults are loaded, lets create the default folder anyway
  347.     this._bookmarkNode = this._servicePane.addNode(ROOTNODE,
  348.         this._servicePane.root, true);
  349.   }
  350.   return this.addBookmarkAt(aURL, aTitle, aIconURL, this._bookmarkNode, null);
  351. }
  352.  
  353. sbBookmarks.prototype.addBookmarkAt =
  354. function sbBookmarks_addBookmarkAt(aURL, aTitle, aIconURL, aParent, aBefore) {
  355.   var bnode = this._servicePane.addNode(aURL, aParent, false);
  356.   if (!bnode) {
  357.     return bnode;
  358.   }
  359.   
  360.   SBDataSetBoolValue('browser.canbookmark', false);
  361.   
  362.   bnode.url = aURL;
  363.   bnode.name = aTitle;
  364.   if (aBefore) {
  365.     aBefore.parentNode.insertBefore(bnode, aBefore);
  366.   }
  367.   bnode.properties = "bookmark " + this._makeCSSProperty(aTitle);
  368.   bnode.hidden = false;
  369.   bnode.contractid = CONTRACTID;
  370.   bnode.dndDragTypes = BOOKMARK_DRAG_TYPE;
  371.   bnode.dndAcceptNear = BOOKMARK_DRAG_TYPE;
  372.   bnode.editable = true;
  373.   
  374.   if (aIconURL && aIconURL.match(/^https?:/)) {
  375.     // check that the supplied image url works, otherwise use the default
  376.     var checker = Cc["@mozilla.org/network/urichecker;1"]
  377.       .createInstance(Ci.nsIURIChecker);
  378.     var uri = Cc["@mozilla.org/network/standard-url;1"]
  379.       .createInstance(Ci.nsIURI);
  380.     uri.spec = aIconURL;
  381.     checker.init(uri);
  382.     checker.asyncCheck(new ImageUriCheckerObserver(bnode, aIconURL), null);
  383.   }
  384.  
  385.   return bnode;
  386. }
  387.  
  388. function ImageUriCheckerObserver(bnode, icon) {
  389.   this._bnode = bnode;
  390.   this._icon = icon;
  391. }
  392.  
  393. ImageUriCheckerObserver.prototype.onStartRequest =
  394. function ImageUriCheckerObserver_onStartRequest(aRequest, aContext)
  395. {
  396. }
  397. ImageUriCheckerObserver.prototype.onStopRequest =
  398. function ImageUriCheckerObserver_onStopRequest(aRequest, aContext, aStatusCode)
  399. {
  400.   if (aStatusCode == 0) {
  401.  
  402.     if(aRequest &&
  403.        aRequest.baseChannel &&
  404.        aRequest.baseChannel instanceof Ci.nsIHttpChannel) {
  405.  
  406.       var channel = aRequest.baseChannel.QueryInterface(Ci.nsIHttpChannel);
  407.  
  408.       if (channel) {
  409.         try {
  410.           var contentType = channel.getResponseHeader("content-type");
  411.             
  412.           if (contentType.substr(0,6) != "image/") {
  413.             Cu.reportError("Favicon URL is not an image - content-type = " + 
  414.                            contentType + 
  415.                            "faviconURL = " +
  416.                            this._icon);
  417.             return;
  418.           }
  419.         }
  420.         catch(e) {
  421.           if (Components.lastResult != Cr.NS_ERROR_NOT_AVAILABLE) {
  422.             Cu.reportError(e);
  423.           }
  424.         }
  425.       }
  426.     }
  427.  
  428.     // If the requested image exists, set it as the icon.
  429.     this._bnode.image = this._icon;
  430.  
  431.   }
  432.   
  433.   // Otherwise, we don't set the image property and we get the default from the skin.
  434.   return;
  435. }
  436.  
  437. sbBookmarks.prototype.addFolder =
  438. function sbBookmarks_addFolder(aTitle) {
  439.   return this.addFolderAt(null, aTitle, null, this._servicePane.root, null);
  440. }
  441.  
  442. sbBookmarks.prototype.addFolderAt =
  443. function sbBookmarks_addFolderAt(aId, aTitle, aIconURL, aParent, aBefore) {
  444.   var  fnode = this._servicePane.addNode(aId, aParent, true);  
  445.   fnode.name = aTitle;
  446.   if (aIconURL != null) {
  447.     fnode.image = aIconURL;
  448.   }
  449.   if (aBefore) {
  450.     aBefore.parentNode.insertBefore(fnode, aBefore);
  451.   }
  452.   fnode.properties = "folder " + this._makeCSSProperty(aTitle);
  453.   fnode.hidden = false;
  454.   fnode.contractid = CONTRACTID;
  455.   fnode.dndAcceptIn = BOOKMARK_DRAG_TYPE;
  456.   fnode.editable = false; // folder names are not editable
  457.   
  458.   return fnode;
  459. }
  460.  
  461. sbBookmarks.prototype.bookmarkExists =
  462. function sbBookmarks_bookmarkExists(aURL) {
  463.   var node = this._servicePane.getNode(aURL);
  464.   return (node != null);
  465. }
  466. sbBookmarks.prototype.fillContextMenu =
  467. function sbBookmarks_fillContextMenu(aNode, aContextMenu, aParentWindow) {
  468.   dump ('called fillContextMenu with node: '+aNode+'\n');
  469.  
  470.   if (!aNode.editable) {
  471.     // not editable - don't add any items
  472.     return;
  473.   }
  474.   
  475.   if (aNode.contractid != CONTRACTID) {
  476.     dump('or not...('+aNode.contractid+')\n');
  477.     return;
  478.   }
  479.     
  480.   var document = aContextMenu.ownerDocument;
  481.     
  482.   var item = document.createElement('menuitem');
  483.   item.setAttribute('label',
  484.             this.getString('bookmarks.menu.rename', 'Rename'));
  485.   var service = this; // so we can get to it from the callback
  486.   item.addEventListener('command',
  487.   function bookmark_edit_oncommand (event) {
  488.     var servicepane = aParentWindow.document.getElementById('servicepane');
  489.     servicepane.startEditingNode(aNode);
  490.   }, false);
  491.   aContextMenu.appendChild(item);
  492.  
  493.   item = document.createElement('menuitem');
  494.   item.setAttribute('label',
  495.             this.getString('bookmarks.menu.edit', 'Edit'));
  496.   var service = this; // so we can get to it from the callback
  497.   item.addEventListener('command',
  498.   function bookmark_edit_oncommand (event) {
  499.     dump ('edit properties of: '+aNode.name+'\n');
  500.     aParentWindow.QueryInterface(Ci.nsIDOMWindowInternal);
  501.     /* begin code stolen from windowUtils.js:SBOpenModalDialog */
  502.     /* FIXME: what's that BackscanPause stuff? */
  503.     var chromeFeatures = "chrome,centerscreen,modal=yes,resizable=no";
  504.     var accessibility = SB_NewDataRemote('accessibility.enabled', null).boolValue;
  505.     chromeFeatures += (',titlebar='+accessibility?'yes':'no');
  506.     aParentWindow.openDialog('chrome://songbird/content/xul/editBookmark.xul',
  507.                  'edit_bookmark', chromeFeatures, aNode);
  508.   }, false);
  509.   aContextMenu.appendChild(item);
  510.   
  511.   item = document.createElement('menuitem');
  512.   item.setAttribute('label',
  513.             this.getString('bookmarks.menu.remove', 'Remove'));
  514.   item.addEventListener('command',
  515.   function bookmark_delete_oncommand (event) {
  516.     dump ('delete: '+aNode.name+'\n');
  517.     // FIXME: confirmation dialog, eh??
  518.     service._servicePane.removeNode(aNode);
  519.     
  520.     // handle case where the currently viewed page is being removed
  521.     if(event.view.gBrowser.currentURI.spec == aNode.id)
  522.       SBDataSetBoolValue('browser.canbookmark', true);
  523.   }, false);
  524.   aContextMenu.appendChild(item);
  525. }
  526.  
  527. sbBookmarks.prototype.fillNewItemMenu =
  528. function sbBookmarks_fillNewItemMenu(aNode, aContextMenu, aParentWindow) {
  529.   // XXX lone: disabled; see bug 4387
  530.   /*
  531.   var stringBundle = this._stringBundle;
  532.   function add(id, label, accesskey, oncommand) {
  533.     var menuitem = aContextMenu.ownerDocument.createElement('menuitem');
  534.     menuitem.setAttribute('id', id);
  535.     menuitem.setAttribute('class', 'menuitem-iconic');
  536.     menuitem.setAttribute('label', stringBundle.GetStringFromName(label));
  537.     menuitem.setAttribute('accesskey', stringBundle.GetStringFromName(accesskey));
  538.     menuitem.setAttribute('oncommand', oncommand);
  539.     aContextMenu.appendChild(menuitem);
  540.   }
  541.  
  542.   add('file.folder', 'menu.file.folder', 'menu.file.folder.accesskey', 'doMenu("file.folder")');
  543.   */
  544. }
  545.  
  546. sbBookmarks.prototype.onSelectionChanged=
  547. function sbBookmarks_onSelectionChanged(aNode, aContainer, aParentWindow) {
  548. }
  549.  
  550. sbBookmarks.prototype.canDrop =
  551. function sbBookmarks_canDrop(aNode, aDragSession, aOrientation, aWindow) {
  552.   if (aNode.isContainer) {
  553.     if (aOrientation != 0) {
  554.       // for folders you can only drop on the folder, not before or after
  555.       return false;
  556.     }
  557.   }
  558.   if (aDragSession.isDataFlavorSupported(MOZ_URL_DRAG_TYPE)) {
  559.  
  560.     // Check to ensure that we're not attempting to bookmark chrome URLs.
  561.     var dragData = this._getDragData(aDragSession, MOZ_URL_DRAG_TYPE);
  562.     if(dragData.indexOf("chrome://") == 0) {
  563.       return false;
  564.     }
  565.  
  566.     return true;
  567.   }
  568.   return false;
  569. }
  570. sbBookmarks.prototype._getDragData =
  571. function sbBookmarks__getDragData(aDragSession, aDataType) {
  572.   // create an nsITransferable
  573.   var transferable = Cc["@mozilla.org/widget/transferable;1"].
  574.       createInstance(Ci.nsITransferable);
  575.   // specify what kind of data we want it to contain
  576.   transferable.addDataFlavor(aDataType);
  577.   // ask the drag session to fill the transferable with that data
  578.   aDragSession.getData(transferable, 0);
  579.   // get the data from the transferable
  580.   var data = {};
  581.   var dataLength = {};
  582.   transferable.getTransferData(aDataType, data, dataLength);
  583.   // it's always a string. always.
  584.   data = data.value.QueryInterface(Ci.nsISupportsString);
  585.   return data.toString();
  586. }
  587. sbBookmarks.prototype.onDrop =
  588. function sbBookmarks_onDrop(aNode, aDragSession, aOrientation, aWindow, aWindow) {
  589.   if (aDragSession.isDataFlavorSupported(MOZ_URL_DRAG_TYPE)) {
  590.     var data = this._getDragData(aDragSession, MOZ_URL_DRAG_TYPE).split('\n');
  591.     var url = data[0];
  592.     var text = data[1];
  593.     var parent, before=null;
  594.     if (aNode.isContainer) {
  595.       parent = aNode;
  596.     } else {
  597.       parent = aNode.parentNode;
  598.       before = aNode;
  599.       if (aOrientation == 1) {
  600.         // after
  601.         before = aNode.nextSibling;
  602.       }
  603.     }
  604.     this.addBookmarkAt(url, text, null, parent, before);
  605.   }
  606. }
  607. sbBookmarks.prototype._addDragData =
  608. function sbBookmarks__addDragData (aTransferable, aData, aDataType) {
  609.   aTransferable.addDataFlavor(aDataType);
  610.   var text = Cc["@mozilla.org/supports-string;1"].
  611.      createInstance(Ci.nsISupportsString);
  612.   text.data = aData;
  613.   // double the length - it's unicode - this is stupid
  614.   aTransferable.setTransferData(aDataType, text, text.data.length*2);
  615. }
  616. sbBookmarks.prototype.onDragGesture =
  617. function sbBookmarks_onDragGesture(aNode, aTransferable) {
  618.   if (aNode.isContainer) {
  619.     // you can't drag folders - that should be handled at the
  620.     // service pane service layer
  621.     return false;
  622.   }
  623.   
  624.   // attach a text/x-moz-url
  625.   // this is for dragging to other places
  626.   /*
  627.   this._addDragData(aTransferable, aNode.url+'\n'+aNode.name, MOZ_URL_DRAG_TYPE);
  628.   */
  629.   
  630.   // and say yet - lets do this drag
  631.   return true;
  632. }
  633.  
  634. /**
  635.  * Called when the user has attempted to rename a bookmark node
  636.  */
  637. sbBookmarks.prototype.onRename =
  638. function sbBookmarks_onRename(aNode, aNewName) {
  639.   if (aNode && aNewName) {
  640.     aNode.name = aNewName;
  641.   }
  642. }
  643.  
  644. /**
  645.  * Turn a partial entity (&foo.bar) into a css property string (foo-bar),
  646.  * but leaves other strings as they are.
  647.  */
  648. sbBookmarks.prototype._makeCSSProperty = 
  649. function sbBookmarks__makeCSSProperty(aString) {
  650.   if ( aString[0] == "&" ) {
  651.     aString = aString.substr(1, aString.length);
  652.     aString = aString.replace(/\./g, "-");
  653.   }
  654.   return aString;
  655. }
  656.  
  657. /**
  658.  * /brief XPCOM initialization code
  659.  */
  660. function makeGetModule(CONSTRUCTOR, CID, CLASSNAME, CONTRACTID, CATEGORIES) {
  661.   return function (comMgr, fileSpec) {
  662.     return {
  663.       registerSelf : function (compMgr, fileSpec, location, type) {
  664.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  665.         compMgr.registerFactoryLocation(CID,
  666.                         CLASSNAME,
  667.                         CONTRACTID,
  668.                         fileSpec,
  669.                         location,
  670.                         type);
  671.         if (CATEGORIES && CATEGORIES.length) {
  672.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  673.               .getService(Ci.nsICategoryManager);
  674.           for (var i=0; i<CATEGORIES.length; i++) {
  675.             var e = CATEGORIES[i];
  676.             catman.addCategoryEntry(e.category, e.entry, e.value, 
  677.               true, true);
  678.           }
  679.         }
  680.       },
  681.  
  682.       getClassObject : function (compMgr, cid, iid) {
  683.         if (!cid.equals(CID)) {
  684.           throw Cr.NS_ERROR_NO_INTERFACE;
  685.         }
  686.  
  687.         if (!iid.equals(Ci.nsIFactory)) {
  688.           throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  689.         }
  690.  
  691.         return this._factory;
  692.       },
  693.  
  694.       _factory : {
  695.         createInstance : function (outer, iid) {
  696.           if (outer != null) {
  697.             throw Cr.NS_ERROR_NO_AGGREGATION;
  698.           }
  699.           return (new CONSTRUCTOR()).QueryInterface(iid);
  700.         }
  701.       },
  702.  
  703.       unregisterSelf : function (compMgr, location, type) {
  704.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  705.         compMgr.unregisterFactoryLocation(CID, location);
  706.         if (CATEGORIES && CATEGORIES.length) {
  707.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  708.               .getService(Ci.nsICategoryManager);
  709.           for (var i=0; i<CATEGORIES.length; i++) {
  710.             var e = CATEGORIES[i];
  711.             catman.deleteCategoryEntry(e.category, e.entry, true);
  712.           }
  713.         }
  714.       },
  715.  
  716.       canUnload : function (compMgr) {
  717.         return true;
  718.       },
  719.  
  720.       QueryInterface : function (iid) {
  721.         if ( !iid.equals(Ci.nsIModule) ||
  722.              !iid.equals(Ci.nsISupports) )
  723.           throw Cr.NS_ERROR_NO_INTERFACE;
  724.         return this;
  725.       }
  726.  
  727.     };
  728.   }
  729. }
  730.  
  731. var NSGetModule = makeGetModule (
  732.   sbBookmarks,
  733.   Components.ID("{21e3e540-1191-46b5-a041-d0ab95d4c067}"),
  734.   "Songbird Bookmarks Service",
  735.   CONTRACTID,
  736.   [{
  737.     category: 'service-pane',
  738.     entry: 'bookmarks',
  739.     value: '@songbirdnest.com/servicepane/bookmarks;1'
  740.   }]);
  741.  
  742.  
  743.